package rate_limiter

import (
	"errors"
	"fmt"
	"sync"
	"time"
)

type SlidingWindowLimiter struct {
	//限额
	quota int
	//窗口大小 秒
	window int
	//区块大小 秒
	blockSize int
	//循环数组指针
	pointer int
	//计数
	count int
	//指针移动次数累积
	moveTime int
	//限流器创建时间
	createTime int64
	//循环数组
	counter []int
	sync.Mutex
}

const (
	Allow     = 1
	HitQuota  = 2
	OverQuota = 3
)

// NewSlidingWindowLimiter 秒为单位
func NewSlidingWindowLimiter(limit int, blockSize, window int) (*SlidingWindowLimiter, error) {
	//区块必须大于0
	if blockSize <= 0 {
		return nil, errors.New("blockSize must be greater than 0")
	}
	//窗口必须大于0
	if window <= 0 {
		return nil, errors.New("window must be greater than 0")
	}
	//窗口不能小于区块
	if window < blockSize {
		return nil, errors.New("window must be greater than or equal to blockSize")
	}
	//窗口必须被区块数整除
	if window%blockSize != 0 {
		return nil, fmt.Errorf("window must be a multiple of blockSize")
	}

	return &SlidingWindowLimiter{
		quota:      limit,
		window:     window,
		blockSize:  blockSize,
		counter:    make([]int, window/blockSize),
		createTime: time.Now().Unix(),
	}, nil
}

func (limiter *SlidingWindowLimiter) Take() int {
	limiter.Lock()
	defer limiter.Unlock()
	//获取移动次数并更新累积移动次数
	delta := int(time.Now().Unix() - limiter.createTime)
	move := delta/limiter.blockSize - limiter.moveTime
	limiter.moveTime = delta / limiter.blockSize
	//move过长时直接清空计数
	if move > len(limiter.counter) {
		limiter.clearCount()
		move = move % len(limiter.counter)
	}

	if move == 0 {
		if limiter.count < limiter.quota {
			limiter.incrCount()
		} else {
			return OverQuota
		}
	} else {
		for move > 0 {
			//移动指针
			limiter.movePointer()
			move -= 1

			if move == 0 {
				if limiter.count < limiter.quota {
					limiter.incrCount()
				} else {
					return OverQuota
				}
			}
		}
	}
	//返回状态码
	if limiter.count < limiter.quota {
		return Allow
	} else if limiter.count == limiter.quota {
		return HitQuota
	} else {
		return OverQuota
	}
}

// 计数
func (limiter *SlidingWindowLimiter) incrCount() {
	limiter.counter[limiter.pointer] += 1
	limiter.count += 1
}

// 循环数组指针移动与区块的更迭
func (limiter *SlidingWindowLimiter) movePointer() {
	limiter.pointer = (limiter.pointer + 1) % len(limiter.counter)
	limiter.count -= limiter.counter[limiter.pointer]
	limiter.counter[limiter.pointer] = 0
}

// 清空计数，清空循环数组
func (limiter *SlidingWindowLimiter) clearCount() {
	for i := range limiter.counter {
		limiter.counter[i] = 0
	}
	limiter.count = 0
}
